﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using EI.SI;
using System.Security.Cryptography;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Threading;

namespace SSHsi
{
    public class Gestor
    {
        public const int NOMEFICHEIRO = 20;
        public const int MULTIPART = 21;
        public const int TAMANHOFICHEIRO = 22;

        private int _porta;
        private IPAddress _ip;

        private Thread listenThread = null;

       
        
        //private TcpClient tcpClient= null;
        private TcpListener tcpListener = null;
        //private NetworkStream stream = null;
        private byte[] msg;
        private ProtocolSI protocol = new ProtocolSI();
        private ProtocolSICmdType cmdType = ProtocolSICmdType.ACK;
        private string _pathFicheiro;
        private string _nomeFicheiro;
        private byte[] buffer = new byte[1000];

        SymmetricAlgorithm _3des = null;
        SymmetricsSI symmSI = null;
        RSACryptoServiceProvider rsaClient = null;
        RSACryptoServiceProvider rsaServer = null;

        public Gestor(int porta, string ip)
        {
            this._porta = porta;
            this._ip = IPAddress.Parse(ip);
        }

        

        public void nomeFicheiro(string pathFicheiro, string nomeFicheiro)
        {
            this._pathFicheiro = pathFicheiro;
            this._nomeFicheiro = nomeFicheiro;
        }

        public void enviarFicheiro()
        {

            #region initialize
                TcpClient tcpClient = new TcpClient();
                tcpClient.Connect(this._ip, this._porta);
                NetworkStream stream = tcpClient.GetStream();

                // Symmmetric Algorithm (TDES) & defenitions
                _3des = TripleDESCryptoServiceProvider.Create();
                //sa.GenerateKey();  
                //sa.GenerateIV();
                _3des.Mode = CipherMode.CBC;
                _3des.Padding = PaddingMode.PKCS7;

                //Assymetric algorithim
                rsaClient = new RSACryptoServiceProvider();
                rsaServer = new RSACryptoServiceProvider();
 
            #endregion

                #region Exchange Public Keys
                try
                {
                    
                    //send client public key
                    msg = protocol.Make(ProtocolSICmdType.PUBLIC_KEY, rsaClient.ToXmlString(false));
                    stream.Write(msg, 0, msg.Length);
                    Debug.WriteLine("comeco do enviar");

                    //receive server public key
                    stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                    rsaServer.FromXmlString(protocol.GetStringFromData());
                    
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception: {0}", ex.ToString());
                }
                #endregion


                try
                {
                
                #region Exchange Symetric Info
                //send symetric Key
                byte[] cipherKey = rsaServer.Encrypt(_3des.Key, true);
                msg = protocol.Make(ProtocolSICmdType.SECRET_KEY, cipherKey);
                stream.Write(msg, 0, msg.Length);
                // Receive answer from server 
                // waiting for ACK
                stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                
                //send IV
                byte[] cipherIV = rsaServer.Encrypt(_3des.IV, true);
                msg = protocol.Make(ProtocolSICmdType.IV, cipherIV);
                stream.Write(msg, 0, msg.Length);
                //waiting for ACK
                stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                
                //send cipher mode
                byte[] mode = Encoding.UTF8.GetBytes(((int)_3des.Mode).ToString());
                byte[] cipherMode = rsaServer.Encrypt(mode, true);
                msg = protocol.Make(ProtocolSICmdType.MODE, cipherMode);
                stream.Write(msg, 0, msg.Length);
                //waiting for ACK
                stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                
                // Send padding mode
                Console.Write("Send Padding");
                byte[] padding = Encoding.UTF8.GetBytes(((int)_3des.Padding).ToString());
                byte[] cipherPadding = rsaServer.Encrypt(padding, true);
                msg = protocol.Make(ProtocolSICmdType.PADDING, cipherPadding);
                stream.Write(msg, 0, msg.Length);
                //waiting for ACK
                stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                Console.Write("received ACK");
                #endregion


                #region Exchange Encrypted Data (Enviar)
                // create Encrytor/Decryptor
                symmSI = new SymmetricsSI(_3des);

                //enviar nome do ficheiro
                byte[] clearData = Encoding.UTF8.GetBytes(_nomeFicheiro);
                byte[] cipherData = symmSI.Encrypt(clearData);
                msg = protocol.Make((ProtocolSICmdType)NOMEFICHEIRO, cipherData);
                stream.Write(msg, 0, msg.Length);
                // waiting for ACK
                stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                
                //enviar ficheiro
                FileStream fsRead = new FileStream(_pathFicheiro, FileMode.Open);
                int n;
                while ((n = fsRead.Read(buffer, 0, buffer.Length)) != 0)
                {
                    cipherData = symmSI.Encrypt(buffer);
                    msg = protocol.Make((ProtocolSICmdType)MULTIPART, cipherData);
                    //enviar para o servidor
                    stream.Write(msg, 0, msg.Length);
 
                    //a espera do ACK
                    n = stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                    Console.WriteLine("recebeu ack");
                    //se receber ACK continuar
                    //if (protocol.GetCmdType() != ProtocolSICmdType.ACK) break;
                }
                fsRead.Close();

                //enviar FINAL DE FICHEIRO para o servidor
                msg = protocol.Make(ProtocolSICmdType.EOF);
                stream.Write(msg, 0, msg.Length);
                #endregion

                #region Signature
                byte[] fileEmBytes = System.IO.File.ReadAllBytes(_pathFicheiro);
                byte[] signature = rsaClient.SignData(fileEmBytes, new SHA1CryptoServiceProvider());
                msg = protocol.Make(ProtocolSICmdType.DIGITAL_SIGNATURE, signature);
                stream.Write(msg, 0, msg.Length);
                //a espera do ACK
                n = stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                #endregion

                
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: {0}", ex.ToString());
            }
            finally
            {
                //enviar FINAL DE TRANSMISSAO
                msg = protocol.Make(ProtocolSICmdType.EOT);
                stream.Write(msg, 0, msg.Length);
                closeAll();
            }
        }

        public void server()
        {
            this.tcpListener = new TcpListener(_ip, _porta);
            this.listenThread = new Thread(new ThreadStart(listenForClients));
            this.listenThread.Start(); 
        }

        public void closeServer()
        {
            this.tcpListener.Stop();
            this.listenThread.Abort();
            this.listenThread.Join();
            this.listenThread = null;

        }

        public void listenForClients()
        {
            this.tcpListener.Start();

            while (true)
            {
                //blocks until a client has connected to the server
                TcpClient client = this.tcpListener.AcceptTcpClient();

                //create a thread to handle communication 
                //with connected client
                Thread clientThread = new Thread(new ParameterizedThreadStart(receberFicheiro));
                clientThread.Start(client);
            }

        }

        public void receberFicheiro(object client)
        {

            TcpClient tcpClient = (TcpClient)client;
            NetworkStream stream = tcpClient.GetStream();

            try {
            #region Iniciar
            rsaClient = new RSACryptoServiceProvider();
            rsaServer = new RSACryptoServiceProvider();

            // Symmmetric Algorithm (TDES)
            _3des = TripleDESCryptoServiceProvider.Create();

            #endregion


            #region Exchange Public Keys
            // Receive Client Public Key
            stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
            rsaClient.FromXmlString(protocol.GetStringFromData());
            // Send Server public key
            msg = protocol.Make(ProtocolSICmdType.PUBLIC_KEY, rsaServer.ToXmlString(false));
            stream.Write(msg, 0, msg.Length);
            #endregion

            #region Exchange Symetric Info

            //receive secret key
            stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
            _3des.Key = rsaServer.Decrypt(protocol.GetData(), true);
            // Answer with a ACK
            msg = protocol.Make(ProtocolSICmdType.ACK);
            stream.Write(msg, 0, msg.Length);

            // Receive IV
            stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
            _3des.IV = rsaServer.Decrypt(protocol.GetData(), true);
            // Answer with a ACK
            msg = protocol.Make(ProtocolSICmdType.ACK);
            stream.Write(msg, 0, msg.Length);

            // Receive CiperMode
            stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
            byte[] mode = rsaServer.Decrypt(protocol.GetData(), true);
            _3des.Mode = (CipherMode)int.Parse(Encoding.UTF8.GetString(mode));
            // Answer with a ACK
            msg = protocol.Make(ProtocolSICmdType.ACK);
            stream.Write(msg, 0, msg.Length);

            // Receive PaddingMode
            stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
            byte[] padding = rsaServer.Decrypt(protocol.GetData(), true);
            _3des.Padding = (PaddingMode)int.Parse(Encoding.UTF8.GetString(padding));
            // Answer with a ACK
            msg = protocol.Make(ProtocolSICmdType.ACK);
            stream.Write(msg, 0, msg.Length);
            #endregion


            #region Exchange Data (Secure Channel) (Receber)
            // create Encrytor/Decryptor
            symmSI = new SymmetricsSI(_3des);

            int n;
            FileStream fsWrite = null;
            byte[] clearData;

            do{
                n = stream.Read(protocol.Buffer, 0, protocol.Buffer.Length);
                cmdType = protocol.GetCmdType();

                if (cmdType == (ProtocolSICmdType)NOMEFICHEIRO)
                {
                    clearData = symmSI.Decrypt(protocol.GetData());
                    _nomeFicheiro = ProtocolSI.ToString(clearData);
                    fsWrite = new FileStream(_nomeFicheiro, FileMode.Create, FileAccess.Write);
                    //enviar ACK
                    msg = protocol.Make(ProtocolSICmdType.ACK);
                    stream.Write(msg, 0, msg.Length);

                }
                else if (cmdType == (ProtocolSICmdType)MULTIPART)
                {
                    clearData = symmSI.Decrypt(protocol.GetData());
                    fsWrite.Write(clearData, 0, clearData.Length);
                    //enviar ACK
                    msg = protocol.Make(ProtocolSICmdType.ACK);
                    stream.Write(msg, 0, msg.Length);
                }
                else if (cmdType == ProtocolSICmdType.EOF)
                {
                    fsWrite.Close();
                    //send ack
                    msg = protocol.Make(ProtocolSICmdType.ACK);
                    stream.Write(msg, 0, msg.Length);
                }
                else if (cmdType == ProtocolSICmdType.DIGITAL_SIGNATURE)
                {
                    byte[] signature = protocol.GetData();
                    byte[] fileEmBytes = System.IO.File.ReadAllBytes(_nomeFicheiro);
                    bool status = rsaClient.VerifyData(fileEmBytes, new SHA1CryptoServiceProvider(), signature);
                    if (status)
                    {
                        msg = protocol.Make(ProtocolSICmdType.ACK);
                    }
                    else
                    {
                        msg = protocol.Make(ProtocolSICmdType.NACK);
                    }
                    stream.Write(msg, 0, msg.Length);
                }

            } while (cmdType != ProtocolSICmdType.EOT);

            #endregion

            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: {0}", ex.ToString());
            }
            finally
            {
                if (symmSI != null) symmSI.Dispose();
                if (_3des != null) _3des.Dispose();
                if (rsaClient != null) rsaClient.Dispose();
                if (rsaServer != null) rsaServer.Dispose();
                stream.Close();
                tcpClient.Close(); 
                
            }
            
        }

        public void closeAll()
        {
            if (symmSI != null) symmSI.Dispose();
            if (_3des != null) _3des.Dispose();
            if (rsaClient != null) rsaClient.Dispose();
            if (rsaServer != null) rsaServer.Dispose();
            //if (this.stream != null) this.stream.Close();
            //if (tcpClient != null) this.tcpClient.Close();
            if (this.tcpListener != null) this.tcpListener.Stop();
        }

        public string getRespostaProtocolo()
        {
            return this.protocol.GetStringFromData();
        }

    }
}
